<?php

require_once(dirname(__FILE__)."/../OAuth/OAuth.php");

class MySpaceException extends Exception {
  const TOKEN_REQUIRED = 1;
  const REMOTE_ERROR   = 2;
  const REQUEST_FAILED = 3;
  const CONNECT_FAILED = 4;

  public $response;
  
  public static $MS_DUMP_REQUESTS = './ms.error.log';

  function __construct($msg, $code, $response=null) {
    parent::__construct($msg, $code);
    $this->response = $response;
    
    $datetime = new DateTime();
    $datetime =  $datetime->format(DATE_ATOM);
    
    file_put_contents(self::$MS_DUMP_REQUESTS, 
	    "\r\n====================================================\r\n".
	    "time: $datetime\r\n" .
	    "message:\r\n--------------------------\r\n$msg\r\n\r\n" .
	    "code:\r\n----------------------------\r\n$code\r\n\r\n" .
	    "response:\r\n----------------------------\r\n$response\r\n\r\n",
			FILE_APPEND);
    
  }
}

class MySpace {


/**
* @access public
*/
  public static $MS_API_ROOT = "http://api.myspace.com";

/**
* @access public
*/
  public static $MS_DUMP_REQUESTS = './ms.log';
  // set to a pathname to dump out http requests to a log. For example, "./ms.log"

  // OAuth URLs
  protected function requestTokenURL() { return self::$MS_API_ROOT.'/request_token'; }
  protected function authorizeURL() { return self::$MS_API_ROOT.'/authorize'; }
  protected function accessTokenURL() { return self::$MS_API_ROOT.'/access_token'; }


  //new external functions

  /**
   * RESTurl: http://api.myspace.com/v1/user
   * Gets the MySpace UserId of the user represented by the token
   *
   * @return String value extracted from the user response
   */
  public function getCurrentUserId(){
  	$REST = "http://api.myspace.com/v1/user".'.json';
  	$params = NULL;

  	$responseJSON = $this->call($REST,$params, 'GET');

  	//parseJSON is a static method and does not effect the object
  	$currentUser = self::parseJSON($responseJSON);
  	return $currentUser->userId;
  }

  /**
   * REST Notes: http://api.myspace.com/v1/users/{userId}/albums
   *
   * Gets the MySpace UserId of the user represented by the tokenReturns the photo
   *  album data for the user specified by userid.
   *
   * Returns the dynamically generated "Albums" "shallow object" that contains
   *  the user's photo album information. The field names of the object will be
   *  identical to those that are returned in a direct REST API call.
   *
   *
   * @param $userId: The user's MySpace user ID
   * @param $page: Specifies the sequential page number starting from 1. If not specified, defaults to 1
   * @param $pageSize: Specifies the number of albumsper page. If not specified, defaults to 20. Setting the pageSize parameter to "all" will give the entire list
   *
   * @return String value extracted from the response
   */
  public function getAlbums($userId, $page=1, $pageSize=20){
  	//TODO:validate($userId)

  	$REST = "http://api.myspace.com/v1/users/".$userId."/albums".'.json';


  	//set the params
  	$params = array( //if $page is null or empty, use the default, else use $page
  				'page' => (empty($page) ? 1 : $page),
  				'pageSize' => (empty($pageSize) ? 20 : $pageSize)
  				);



  	$responseJSON = $this->call($REST,$params, 'GET');

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);
  }

  /**
   * The function will get all of the activites of a given user, provided that the current user and application have privilages to access it
   *
   * @param int|string $userId
   * @param unknown_type $culture
   * @param unknown_type $lastRetrievalTimeStamp
   * @param string $activityTypes 'EventAttending','EventPosting','ProfileSongAdd','FriendAdd','FriendCategoryAdd','ForumPosted','JoinedGroup','ForumTopicReply','ProfileVideoUpdate','FavoriteVideoAdd','PhotoAdd','MobilePhotoUpload','PhotoTagged','BlogAdd','SongUpload','PersonalBandShowUpdate' 
   * @return SimpleXML a SimpleXML representation of an Activities ATOM Feed
   */
  public function getActivities_ATOM($userId, $culture=null, $lastRetrievalTimeStamp=null, $activityTypes=null){
  	//TODO:validate($userId)
  	//TODO: this api call might require the userid to match the access token.
  	//see getFriendsActivities_ATOM for activity types

  	$REST = "http://api.myspace.com/v1/users/".$userId."/activities.atom";
	//$REST = "http://api.myspace.com/v1/users/".$userId."/activities".'.atom';


  	//set the params
  	$params = array( 'culture' => null,
  					 'lastRetrievalTimeStamp' => null,
  					 'activitytypes' => $activityTypes
  					);

  	$responseATOM = $this->call_ATOM($REST,$params, 'GET');

  	//returns a SimpleXML Atom feed.
  	return $responseATOM;
  }
  
  /**
   * The function will get all of the activites of a given user's friends, provided that the current user and application have privilages to access it
   *
   * @param int|string $userId
   * @param unknown_type $culture
   * @param unknown_type $lastRetrievalTimeStamp
   * @param string $activityTypes 'EventAttending','EventPosting','ProfileSongAdd','FriendAdd','FriendCategoryAdd','ForumPosted','JoinedGroup','ForumTopicReply','ProfileVideoUpdate','FavoriteVideoAdd','PhotoAdd','MobilePhotoUpload','PhotoTagged','BlogAdd','SongUpload','PersonalBandShowUpdate' 
   * @return SimpleXML a SimpleXML representation of an Activities ATOM Feed
   */
  public function getFriendsActivities_ATOM($userId, $culture=null, $lastRetrievalTimeStamp=null, $activityTypes=null){
  	//TODO:validate($userId)
  	//TODO: this api call might require the userid to match the access token.

  	$REST = "http://api.myspace.com/v1/users/".$userId."/friends/activities.atom";
	

  	//culture, lastRetrievalTimeStamp(optional), activitytypes(optional)
  	//set the params
  	$params = array( 'culture' => null,
  					 'lastRetrievalTimeStamp' => null,
  					 'activitytypes' => $activityTypes
  					);

	$responseATOM = $this->call_ATOM($REST,$params, 'GET');


  	//returns a SimpleXML Atom feed.
  	return $responseATOM;
  }

  /**
   * @access private
   * creates an url encoded param array from an associative array
   *
   * @param array $params
   * @return returns a proper urlEncoded parameter array
   */
  private function createParamsString(array $params){
  	//TODO: url encode (values)

  	$paramsStr = '';

  	foreach($params as $key => $value){
  		$paramsStr .= $k.'='.$value.'&';
  	}

  	$paramsStr = substr($paramsStr, 0, -1); //remove the trailing '&' for correctness

  	return $paramsStr;

  }


  /**
   * retrieves all photos for a user's album
   *
   * @param String $userId
   * @param String $albumId
   * @return object a php object representing the JSON
   */
  public function getAlbum($userId, $albumId){
  	//TODO: validate($userId, $albumId)

  	$REST = "http://api.myspace.com/v1/users/".$userId."/albums/".$albumId."/photos".'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  /**
   * Enter description here...
   *
   * @param String $userId
   * @param int $page
   * @param int $pageSize
   * @param String $list 'top,online,app' must select top, online or app, top is the default response
   * @param String $show 'mood|status|online' can set some combintion there of via vertical pipes '|'
   * @return object a php object representing the JSON which is list of the current user's friends
   */
  public function getFriends(
  					$userId,
  					$page=1,
  					$pageSize=20,
  					$list=NULL,
  					$show='mood|status|online'){

  	$REST = "http://api.myspace.com/v1/users/".$userId."/friends".'.json';

  	//set the params
  	$params = array(
  				'page' => (empty($page) ? 1 : $page), //if $page is null or empty, use the default, else use $page
  				'pageSize' => (empty($pageSize) ? 20 : $pageSize),
  				'list' => $list,
  				'show' => $show
  				);

  	$responseJSON = $this->call($REST, $params, 'GET');

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);


  }

  /**
   * describes if $userId and $friendsId are currently friends
   *
   * @param String $userId
   * @param int $friendsId
   * @return object an object with a boolean that describes if $userId and $friendsId are currently friends
   */
  public function getFriendship($userId, $friendsId){
  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/friends/'.$friendsId.'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  /**
   * gets the mood of the current user
   *
   * @param String $userId
   * @return object the mood of the current user
   */
  public function getMood($userId){
  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/mood'.'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  /**
   * gets a list of all of the current users photos
   *
   * @param String $userId
   * @param int $page
   * @param int $pageSize
   * @return object a list containing all of the current users photos
   */
  public function getPhotos( $userId, $page=1, $pageSize=20){

  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/photos'.'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  /**
   * gets the meta data for a specific photoid
   *
   * @param String $userId
   * @param String $photoId
   * @return object the meta data for a specific photoid
   */
  public function getPhoto( $userId, $photoId){
  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/photos/'.$photoId.'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  /**
   * gets a user's profile, the object type changes depending on the detail type
   * you are better off useing getProfileBasic(), getProfileFull(), or getProfileExtended()
   *
   * @param String $userId
   * @param String $detailtype 'full' or 'basic' or 'extended' determines request and return type
   * @return mixed a users profile, which can be of type ProfileFull, ProfileBasic, or ProfileExtended
   */
  public function getProfile( $userId, $detailtype = 'full' ){
  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/profile'.'.json';
	
	//default to full, TODO: maybe should raise error?
	$detailType = ($detailtype == 'full'||'extended'||'basic') ? $detailtype : 'full';
	
	//requires a GET request, POST does not work
  	$responseJSON = $this->call($REST, array('detailtype' => $detailType), 'GET' );

	//requires a GET request, POST does not work

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }
  /**
  * gets a basic user profile given the userid
  * @param string $userId
  * @return ProfileBasic
  */
  public function getProfileBasic( $userId ){ getProfile($userId, 'basic'); }
  /**
  * gets a full user profile given the userid
  * @param string $userId
  * @return ProfileFull
  */
  public function getProfileFull( $userId ){ getProfile($userId, 'full'); }
  /**
  * gets an extended user profile given a userid
  * @param string $userId
  * @return ProfileExtended
  */
  public function getProfileExtended( $userId ){ getProfile($userId, 'extended'); }

  /**
   * gets a user's status
   *
   * @param String $userId
   * @return object a php object that represents the JSON
   */
  public function getStatus( $userId ){
  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/status'.'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  /**
   * gets a list of the current users videos
   *
   * @param String $userId
   * @return object
   */
  public function getVideos( $userId ){
  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/videos'.'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  /**
   * gets the meta data for a specific userid, and videoid
   *
   * @param String $userId
   * @param String $videoId
   * @return object meta data about a specific video
   */
  public function getVideo( $userId, $videoId ){
  	$REST = 'http://api.myspace.com/v1/users/'.$userId.'/videos/'.$videoId.'.json';

  	$responseJSON = $this->call($REST);

  	//parseJSON is a static method and does not effect the object
  	return self::parseJSON($responseJSON);

  }

  // internal 'private' functions

  /**
   * Sets up the MySpaceID API with your credentials
   *
   * @param String $consumerKey
   * @param String $consumerSecret
   * @param String $oAuthToken
   * @param String $oAuthTokenSecret
   * @return object a new myspace object
   */
  public function __construct($consumerKey,
		       $consumerSecret,
		       $oAuthToken = null,
		       $oAuthTokenSecret = null)  {
    $this->sha1_method = new OAuthSignatureMethod_HMAC_SHA1();
    $this->consumer = new OAuthConsumer($consumerKey, $consumerSecret, NULL);
    if (!empty($oAuthToken)) {
    	$this->token = new OAuthConsumer($oAuthToken, $oAuthTokenSecret);
    } else {
      $this->token = NULL;
    }
  }

    /**
   * Get a request token for authenticating your application with FE.
   *
   * @return a key/value pair array containing: oauth_token and
   * oauth_token_secret.
   */
  public function getRequestToken() {
    $r = $this->oAuthRequest($this->requestTokenURL());

    $token = $this->oAuthParseResponse($r);

    $this->token = new OAuthConsumer($token['oauth_token'], $token['oauth_token_secret']); // use this token from now on

    if (self::$MS_DUMP_REQUESTS){
    	self::dump(
    				"Now the user is redirected to ".$this->getAuthorizeURL($token['oauth_token']).
    				"\nOnce the user returns, via the callback URL for web authentication".
    				" or manually for desktop authentication, we can get their access token".
    				" and secret by calling /oauth/access_token.".
    				"\n\n");
    }
    return $token;
  }


  /**
   * Get the URL to redirect to to authorize the user and validate a
   * request token.
   *
   * @returns a string containing the URL to redirect to.
   */
  public function getAuthorizeURL($token) {
    // $token can be a string, or an array in the format returned by getRequestToken().
    if (is_array($token)) $token = $token['oauth_token'];

	return 	(
	    		$this->authorizeURL() .
	    		'?oauth_token=' .
	    			$token .
	    		'&oauth_callback=http://' .
		    		$_SERVER['HTTP_HOST'] .
		    		$_SERVER['SCRIPT_NAME'] .
	    		'?f=callback'
    		);
  }



   /**
   * Exchange the request token and secret for an access token and
   * secret, to sign API calls.
   *
   * @param RequestToken $token
   * @return array("oauth_token" => the access token,
   *                "oauth_token_secret" => the access secret)
   */
  public function getAccessToken($token=NULL) {
    $this->requireToken();

    $r = $this->oAuthRequest($this->accessTokenURL());

    $token = $this->oAuthParseResponse($r);

    $this->token = new OAuthConsumer(
    					$token['oauth_token'],
    					$token['oauth_token_secret']);
    					// use this token from now on

    return $this->token;
  }



  /**
   * the entry point function to call the REST API
   *
   * @param		$url
   * @param		$params
   * @param 	$request_method
   *
   * @return	Object a PHP representation of the returned JSON, XML, or ATOM feed.
   * */
  public function call($url, $params=array(), $request_method=NULL) {


    return $this->call_JSON($url, $params, $request_method);
  }

  /**
   *
   *
   * @param		$url
   * @param		$params
   * @param 	$request_method
   *
   * @return	Object a PHP representation of the returned JSON, XML, or ATOM feed.
   * */
  protected function call_JSON($url, $params=array(), $request_method=NULL) {
    $this->requireToken();
    $r = $this->oAuthRequest($url, $params, $request_method);
    return $this->parseJSON($r);
  }

/**
   *
   *
   * @param		$url
   * @param		$params
   * @param 	$request_method
   *
   * @return	Object a PHP representation of the returned JSON, XML, or ATOM feed.
   * */
  protected function call_XML($url, $params=array(), $request_method=NULL) {
    $this->requireToken();
    $r = $this->oAuthRequest($url, $params, $request_method);
    return new SimpleXMLElement($r);
  }

  /**
   *
   *
   * @param		$url
   * @param		$params
   * @param 	$request_method
   *
   * @return	Object a PHP representation of the returned JSON, XML, or ATOM feed.
   * */
  protected function call_ATOM($url, $params=array(), $request_method=NULL) {
    $this->requireToken();
    $r = $this->oAuthRequest($url, $params, $request_method);;
    return new SimpleXMLElement(mb_convert_encoding($r,'UTF-8', " UTF-8, ASCII, ISO-8859-1, EUC-JP,  SJIS, JIS"));
  }

  /**
   *
   * @param $json a java script object
   *
   * @return Object - a PHP object that represents the JSON
   * */
  protected function parseJSON($json) {
  	if(gettype($json)=="object"){
  		return $json;
  	}

    $r = json_decode($json);

    if (empty($r)){
    	throw new MySpaceException("Empty JSON response",
    								MySpaceException::REQUEST_FAILED);
    }

    if (isset($r->rsp) && $r->rsp->stat != 'ok') {
	    throw new MySpaceException(
    								$r->rsp->code.": ".$r->rsp->message,
    								MySpaceException::REMOTE_ERROR,
    								$r->rsp
    							);
    }
    return $r;
  }

  /**
   * checks if token is present, else throws an exception
   *
   * */
  protected function requireToken() {
    if (!isset($this->token)) {
      throw new MySpaceException(
      				"This function requires an OAuth token",
      				 MySpaceException::TOKEN_REQUIRED
      				 );
    }
  }

  /** Parse a URL-encoded OAuth response
   * @param 	$responseString
   * @return 	Hash Map
   * */
  protected function oAuthParseResponse($responseString) {
    $r = array();
    foreach (explode('&', $responseString) as $param) {

      $pair = explode('=', $param, 2);

      if (count($pair) != 2) continue;

      $r[urldecode($pair[0])] = urldecode($pair[1]);

    }
    return $r;
  }

  /** Format and sign an OAuth / API request
   * @param 	$url
   * @param 	$args
   * @param 	$method
   *
   * */
  function oAuthRequest($url, $args=array(), $method=NULL) {
    if (empty($method)) $method = empty($args) ? "GET" : "POST";

    $req = OAuthRequest::from_consumer_and_token(
	    				$this->consumer,
	    				$this->token,
	    				$method,
	    				$url,
	    				$args);

    $req->sign_request($this->sha1_method, $this->consumer, $this->token);

    if (self::$MS_DUMP_REQUESTS) {

      $k = $this->consumer->secret . "&";

      if ($this->token) $k .= $this->token->secret;

      self::dump("---\n\nOAUTH REQUEST TO $url");

      if (!empty($args)) self::dump(" WITH PARAMS ".json_encode($args));

      self::dump(
      			"\n\nBase string: ".$req->base_string.
      			"\nSignature string: $k\n"
      			);

    }
    switch ($method) {
	    case 'GET':
	    	return $this->http($req->to_url());
	    	break;
	    case 'POST':
	    	return $this->http(
			    		$req->get_normalized_http_url(),
			    		$req->to_postdata()
	    			);
	    	break;
    }
  }

  /**
   *  Make an HTTP request, throwing an exception if we get anything other than a 200 response
   * @param 	$url
   * @param 	$postData
   *
   * @return	$response
   * */
  public function http($url, $postData=null) {
    if (self::$MS_DUMP_REQUESTS) {

      self::dump("Final URL: $url\n\n");

      $url_bits = parse_url($url);

      if (isset($postData)) {

		self::dump(
					"POST ".$url_bits['path']." HTTP/1.0".
					"\nHost: ".$url_bits['host'].
					"\nContent-Type: application/x-www-urlencoded".
					"\nContent-Length: ".strlen($postData).
					"\n\n$postData\n"
					);

      }
      else {

		$get_url = $url_bits['path'];

		if ($url_bits['query']) $get_url .= '?' . $url_bits['query'];

		self::dump(
				"GET $get_url HTTP/1.0".
				"\nHost: ".$url_bits['host'].
				"\n\n"
				);

      }
    }

    $ch = curl_init();

    if (defined("CURL_CA_BUNDLE_PATH")) curl_setopt($ch, CURLOPT_CAINFO, CURL_CA_BUNDLE_PATH);

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    curl_setopt($ch, CURLOPT_TIMEOUT, 30);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, true);

    if (isset($postData)) {
      curl_setopt($ch, CURLOPT_POST, 1);
      curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    }

    $data = curl_exec($ch);
    $response = '';
    list($header, $response) = explode("\r\n\r\n", $data, 2);
    $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $ct = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

    if ($ct) $ct = preg_replace("/;.*/", "", $ct); // strip off charset

    if (!$status) throw new MySpaceException("Connection to $url failed\r\n\r\n<br /><br /><pre>\r\n$header\r\n</pre>",
    					MySpaceException::CONNECT_FAILED);

    //things were not perfect, let's throw an error
    if ($status != 200) {
	//redirects should be valid REST responses!
	    
      switch($status){
      	case 201:
		//i remember seeing this once before
		break;
      	case 401:
		//suspended app
		//insuffecient app permission
		//insuffecient user or application permission
		//incorrect user
		//missing or revoked token
		//insuffecient OpenCanvace Permissions
		//user has not added app
		//expired timestamp
	      break;
      	case 403:
		//missing oauth params
		//expired timestamp
		//used nonce
		
		//invalid key
		
		//invalid token
		
		//invalid key
		/*
		It seems like the openid realm entered in the application page needs
		to have a trailing slash to work properly.  If that’s the case, we
		really need to either add it ourselves or warn the user when they
		enter it.  Also, when the realm doesn’t match, we return:
		
		403 "Neither the token nor the cookie is present to complete this call."
		*/
		break;
	case 404:
		//the resource is not found
		//missing user
		break;
      	case 500:
		//this is likely to be a realm mismatch error, but it could be something else
	      break;
      }
      
      //ok, so its an unexpected error type, find the content type and throw a general error
      if ($ct == "application/json") {
		$r = json_decode($response);
		if ($r && isset($r->rsp) && $r->rsp->stat != 'ok') {

		  throw new MySpaceException($r->rsp->code.": ".$r->rsp->message."\r\n\r\n<br /><br /><pre>\r\n$header\r\n</pre>",
		  				MySpaceException::REMOTE_ERROR, $r->rsp);

		}
      }
      if ($ct == "application/atom+xml") {
	      	//we must have asked for an XML or ATOM doc type, but something went wrong
		$r = new SimpleXMLElement($response);
      }
      
      //throw a general error
      throw new MySpaceException("Request to $url failed: HTTP error $status ($response)"."\r\n\r\n<br /><br /><pre>\r\n$header\r\n</pre>",
      								 MySpaceException::REQUEST_FAILED, $response);
    }
    if (self::$MS_DUMP_REQUESTS) {
      self::dump("HTTP/1.0 $status OK\n");

      if ($ct) self::dump("Content-Type: $ct\n");

      $cl = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);

      if ($cl) self::dump("Content-Length: $cl\n");

      self::dump("\n$response\n\n");
    }
    curl_close ($ch);

    return $response;
  }

  /**
   * writes to an error log
   *
   * @param string $text
   */
  private function dump($text) {
    if (!self::$MS_DUMP_REQUESTS) throw new Exception(
    	'MySpace::$MS_DUMP_REQUESTS must be set to enable request trace dumping');

    file_put_contents(self::$MS_DUMP_REQUESTS, $text, FILE_APPEND);

  }

}

?>
